/*
 * main.c
 *
 *  Created on: 21.11.2011
 *      Author: Pavel Sinay
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <ifaddrs.h>
#include <linux/if_arp.h>
#include <netdb.h>
#include <linux/icmp.h>
#include <netinet/ip.h>

uint16_t checksum(uint16_t *data, size_t length) {
	uint32_t sum = 0;
	while (length != 0) {
		sum += *data;
		data++;
		length -= 2;
	}
	sum = (sum >> 16) + (sum & 0xFFFF);
	sum += sum >> 16;
	return ~sum;
}

int main(int argc, char* argv[]) {
	int fd_socket;
	struct sockaddr_in dest, host;
	struct timeval timeout;
	uint16_t packet_number;
	int id;
	char buf[1024];

	printf("Ping utility\n");

	if (argc < 2) {
		printf("Usage: <IP>\n");
		return 1;
	}

	if (inet_aton(argv[1], &dest.sin_addr) < 0) {
		printf("Invalid IP address\n");
		return 1;
	}

	dest.sin_family = AF_INET;
	dest.sin_port = 0;

	host.sin_family = AF_INET;
	host.sin_port = 0;
	host.sin_addr.s_addr = INADDR_ANY;

	fd_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (fd_socket == -1) {
		perror("socket");
		return 1;
	}
	if (bind(fd_socket, (struct sockaddr*) &host, sizeof(host)) == -1) {
		perror("bind");
		close(fd_socket);
		return 1;
	}
	if (connect(fd_socket, (struct sockaddr*) &dest, sizeof(dest)) == -1) {
		perror("connect");
		close(fd_socket);
		return 1;
	}

	struct icmp_filter flt;
	flt.data = ~((1 << ICMP_SOURCE_QUENCH) | (1 << ICMP_DEST_UNREACH) | (1
			<< ICMP_TIME_EXCEEDED) | (1 << ICMP_PARAMETERPROB) | (1
			<< ICMP_REDIRECT) | (1 << ICMP_ECHOREPLY));
	if (setsockopt(fd_socket, SOL_RAW, ICMP_FILTER, &flt, sizeof(flt))) {
		perror("setsockopt");
	}

	timeout.tv_sec = 0;
	timeout.tv_usec = 100000;
	setsockopt(fd_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
	setsockopt(fd_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

	int val = 1;
	setsockopt(fd_socket, SOL_IP, IP_RECVERR, &val, sizeof(val));
	val = 0;
	setsockopt(fd_socket, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val));
	val = 2048;
	setsockopt(fd_socket, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val));

	packet_number = 0;
	id = getpid() & 0xFFFF;
	for (;;) {
		struct icmphdr* icmp_msg = (struct icmphdr*) buf;

		packet_number++;

		icmp_msg->type = ICMP_ECHO;
		icmp_msg->code = 0;
		icmp_msg->un.echo.id = id;
		icmp_msg->un.echo.sequence = htons(packet_number);
		icmp_msg->checksum = 0;
		icmp_msg->checksum = checksum((char*) buf, sizeof(buf));

		printf("Echo %d sending\n", packet_number);
		if (send(fd_socket, buf, sizeof(buf), 0) == -1) {
			perror("Echo sending error\n");
			close(fd_socket);
			return 1;
		}

		sleep(1);

		if (recv(fd_socket, buf, sizeof(buf), 0) == -1) {
			perror("Echo error");
			continue;
		}

		struct iphdr* iph = buf;

		if (iph->protocol == IPPROTO_ICMP) {
			struct icmphdr* response = (struct icmphdr*) (buf
					+ sizeof(struct iphdr) + iph->frag_off);
			if (response->type == ICMP_ECHOREPLY) {
				printf("Echo %d OK\n", htons(response->un.echo.sequence));
				id++;
			}
		}
	}

	close(fd_socket);
	return 0;
}
